/*
    日志落地模块
    1. 抽象日志落地器基类
    2. 根据不同落地方向进行派生
    3. 使用工厂模式分离创建和表示
*/

#pragma once

#include "util.hpp"
#include <iostream>
#include <memory>
#include <string>
#include <fstream>
#include <sstream>
#include <cassert>

namespace easylog
{
    //日志落地器基类
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        //构造函数传入具体颜色
        LogSink(util::color::color_enum color = util::color::color_enum::none)
               :_color(color)
               {}
        virtual ~LogSink(){}
        //统一落地接口，扩展落地方向必须重写该接口
        virtual void log(const char* data, size_t len) = 0;
        //改变控制台颜色——提供给标准输出落地方向使用
        void changeColor(util::color::color_enum color)
        {
            _color = color;
        }
    protected:
        util::color::color_enum _color;//控制台字体颜色
    };
    //落地方向： 标准输出
    class StdOutSink : public LogSink
    {
    public:
        StdOutSink(util::color::color_enum color = util::color::color_enum::none)
        :LogSink(color)
        {}

        //将日志输出到标准输出
        void log(const char* data, size_t len) override
        {
            if(_color == util::color::color_enum::none)
                std::cout.write(data, len);
            else
            {
                std::string msg = data;//日志消息
                std::string color = util::color::onclr(_color);//颜色转义符
                std::string color_end = util::color::offclr();//none转义符
                msg = color + msg + color_end;
                std::cout.write(msg.c_str(), color.size() + len + color_end.size());
            }
        }
    };
    //落地方向： 指定文件
    class FileSink : public LogSink
    {
    public:
        //构造时创建/打开文件，并保留句柄
        FileSink(const std::string pathname)
        :_pathname(pathname)
        {
            //1.创建文件所需目录
            util::File::createPath(util::File::getPath(_pathname));
            //2.创建文件
            _ofs.open(_pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }
        //将日志输出到指定文件
        void log(const char* data, size_t len) override
        {
            _ofs.write(data, len);
            _ofs.flush();
            assert(_ofs.good());
        }
    private:
        std::string _pathname;//指定文件
        std::ofstream _ofs;//文件句柄
    };
    //滚动文件的基类
    class RollSink : public LogSink
    {
    public:
        //标记以大小滚动，还是时间滚动
        enum class _state
        {
            SIZE = 0,//大小
            TIME//时间
        };
        RollSink(const std::string basename,
                size_t max_size,
                size_t live_time)
        :_basename(basename),
        _max_size(max_size),
        _live_time(live_time),
        _name_count(0),
        _timestamp(0)
        {}
        //日志落地
        virtual void log(const char* data, size_t len) = 0;
        //创建新文件
        bool createNewFile(_state state)
        {
            time_t t = _timestamp = util::Date::getTime();
            struct tm lt;
            localtime_r(&t, &lt);
            std::stringstream filename;
            filename << _basename;
            filename << lt.tm_year + 1900;
            filename << lt.tm_mon + 1;
            filename << lt.tm_mday;
            filename << lt.tm_hour;
            filename << lt.tm_min;
            filename << lt.tm_sec;
            if(state == _state::SIZE)
                filename << "size-";
            else if(state == _state::TIME)
                filename << "time-";
            filename << _name_count++;
            filename << ".log";
            //1.创建文件所需目录
            util::File::createPath(util::File::getPath(filename.str()));
            //2.创建/打开文件
            _ofs.open(filename.str(), std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
            return true;
        }
    protected:
        std::string _basename;//基础文件名   实际文件名：基础 + 扩展(时间)  例如：./logs/base20221212143020.log
        std::ofstream _ofs;//落地文件的句柄
        size_t _max_size;//文件最大大小
        size_t _cur_size;//当前句柄文件的大小
        size_t _live_time;//文件切换时间
        time_t _timestamp;//当前文件的时间戳
        size_t _name_count;//文件名计数器
    };
    //落地方向： 滚动文件(按大小滚动)
    class RollBySizeSink : public RollSink
    {
    public:
        //构造时创建/打开文件，并保留句柄
        RollBySizeSink(const std::string basename, 
                       size_t max_size,
                       size_t live_time)
                       :RollSink(basename, max_size, live_time)
        {
            createNewFile(_state::SIZE);
        }
        //将日志输出到指定文件，输出前要判断是否超过限制大小
        void log(const char* data, size_t len) override
        {
            //先判断是否超过限制大小
            if(_cur_size > _max_size)
            {
                _ofs.close();//关闭原先文件
                createNewFile(_state::SIZE);//切换文件
                _cur_size = 0;//重置文件大小
            }
            _ofs.write(data, len);
            _ofs.flush();
            assert(_ofs.good());
            _cur_size += len;
        }  
    }; 
    //落地方向： 滚动文件(按时间滚动)
    class RollByTimeSink : public RollSink
    {
    public:
        //构造时创建/打开文件，并保留句柄
        RollByTimeSink(const std::string basename, 
                       size_t max_size,
                       size_t live_time)
                       :RollSink(basename, max_size, live_time)
        {
            createNewFile(_state::TIME);       
        }
        //将日志输出到指定文件，输出前要判断是否超过限制大小
        void log(const char* data, size_t len) override
        {
            time_t cur_time = time(nullptr);
            if(cur_time - _timestamp >= _live_time)
            {
                _ofs.close();
                createNewFile(_state::TIME);
            }
            _ofs.write(data, len);
            _ofs.flush();
            assert(_ofs.good());
        }
    };
    
    //落地器工厂
    class SinkFactory
    {
    public:
        template<typename SinkType, typename... Args>
        static LogSink::ptr create(Args&&... args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}